<!DOCTYPE html>
<html lang="en">
  <!--

    Define:

      0 1 1 2 3 4 4 5 
    n 0 0 0 0 1 1 1 1 
    p 0 0 1 1 0 0 1 1
    b 0 1 0 1 0 1 0 1

    Matrix:

      0 1 2 3 4 5
    0 0 0 1 1 2 2
    2 0 0 1 1 2 2
    4 0 0 1 1 2 2
    1 3 3 4 4 5 5
    3 3 3 4 4 5 5
    5 3 3 4 4 5 5


    /*======================================================================================*

            P
            ↓
            
    N →         0   1   2   3   4   5   6
            0   0   0   1   1   2   2   6
            2   0   0   1   1   2   2   6
            4   0   0   1   1   2   2   6
            1   3   3   4   4   5   5   4
            3   3   3   4   4   5   5   4
            5   3   3   4   4   5   5   4
            6   -   -   -   -   -   -   6

            x   a       x   c       y   b       x   b       y   a       y   c       x   b
             \ /         \ /         \ /         \ /         \ /         \ /         \ /
              0           2           4           1           3           5           6
             / \         / \         / \         / \         / \         / \         / \
            a   x       b   x       c   x       a   y       b   y       c   y       x   b


            x   b       x   b
              1   <===>   6
            a   y       x   b
    *======================================================================================*/


    Swap:
      0=>0,1=>3,2=>1,4=>2,3=>4,5=>5
    Convert:
      h' = h === 0 ? 0: 5 - ((h * 2) % 5) 

    Matrix':

      0 3 1 4 2 5                       0 3 1 4 2 5
    0 0 0 3 3 1 1                     0 0 0 3 3 1 1
    3 4 4 2 2 5 5     Change Order    1 0 0 3 3 1 1
    1 0 0 3 3 1 1  >> >> >> >> >> >>  2 0 0 3 3 1 1
    4 4 4 2 2 5 5   Equle to Tenerry  3 4 4 2 2 5 5
    2 0 0 3 3 1 1                     4 4 4 2 2 5 5
    5 4 4 2 2 5 5                     5 4 4 2 2 5 5
  
  -->
  <head>
    <meta charset="utf-8" />
    <!-- <meta http-equiv="Content-Type" content="text/html" /> -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>3n + 1 Game in Byte</title>
  </head>
  <style>
    :root {
      --FONT-SIZE: 18px;
      --C-WHITE: #f3f3f3;
      --C-BLACK: #333;
      --C-RED: #f99;
      --C-GREEN: #9f9;
      --C-BLUE: #99f;
      --C-GRAY: #666;
    }

    body {
      overflow: scroll;
      background-color: var(--C-BLACK);
      color: var(--C-WHITE);
      font-family: Consolas, "Courier New", monospace;
    }

    #rows {
      display: flex;
      flex-flow: row wrap;
      /* place-content: start end; */
      place-items: start start;
      width: 100%;
      margin: 0 auto;
      padding: 0;

      .row {
        display: flex;
        min-width: 6em;
        margin: 0.5em 0 0.5em 0.5em;
        flex-flow: row nowrap;
        /* place-items: center center; */
        place-content: center right;
        font-size: var(--FONT-SIZE);

        .box {
          .prev {
            color: var(--C-GREEN);
          }
          .next {
            color: var(--C-BLUE);
          }
          .brow {
            color: var(--C-RED);
          }

          &.cur {
            background-color: var(--C-GRAY);
          }
        }

        &.org {
          background-color: var(--C-WHITE);
          .box {
            color: var(--C-BLACK);
          }
        }
      }
    }

    #code {
      text-align: center;
    }
  </style>

  <body>
    <pre id="code"></pre>
    <div id="rows"></div>
  </body>

  <script>
    "use strict";

    const MAX_NUM = 1 << 20;
    const GAP_WIDTH = 3;
    const objSum = (obj) => Object.values(obj).reduce((a, b) => a + b, 0);
    const arrNullStr = (arr) => arr.map((e) => (e === null ? "." : e)).join("");
    const code = document.getElementById("code");
    const rows = document.getElementById("rows");

    const listOne = ({ n, p, b }) => {
      const l = n.length;

      for (let i = 1, v = 0; i < l; i++) {
        v = n[i] + p[i] + b[i];

        p[i - 1] = n[i];
        b[i - 1] = (v & 2) >> 1;
        n[i] = v & 1;
      }

      return { n, p, b };
    };

    const debugPart = ({ p, n, b }) => `
          ${arrNullStr(p)}
          ${arrNullStr(n)}
          ${arrNullStr(b)}
        `;

    const toHex = ({ n, p, b }) => {
      let rtn = [];
      for (let i = 0; i < n.length; i++) {
        rtn.push((n[i] ?? 0) * 3 + (p[i] ?? 0) + (b[i] ?? 0));
      }
      return rtn;
    };

    const drowPart = (row, part) => {
      for (let i = 0; i < part.n.length; i++) {
        let box = document.createElement("div");
        box.className = "box";

        let next = document.createElement("div");
        next.className = "next";
        next.textContent = part.n[i] ?? ".";
        box.appendChild(next);
        let prev = document.createElement("div");
        prev.className = "prev";
        prev.textContent = part.p[i] ?? ".";
        box.appendChild(prev);
        let brow = document.createElement("div");
        brow.className = "brow";
        brow.textContent = part.b[i] ?? ".";
        box.appendChild(brow);
        let hex = document.createElement("div");
        hex.textContent = part.h[i] ?? ".";
        box.appendChild(hex);

        row.appendChild(box);
      }
    };

    const drowGap = (row, width = 3, height = 4) => {
      for (let i = 0; i < width; i++) {
        let box = document.createElement("div");
        box.className = "box";

        for (let j = 0; j < height; j++) {
          let gap = document.createElement("div");
          gap.textContent = ".";
          box.appendChild(gap);
        }

        row.appendChild(box);
      }
    };

    (() => {
      const DSet = new Set();

      for (let i = 0; i < 8; i++) {
        for (let j = 0; j < 8; j++) {
          for (let k = 0; k < 8; k++) {
            let A = {
              n: [0, i & 1, j & 1, k & 1],
              p: [0, (i & 2) >> 1, (j & 2) >> 1, (k & 2) >> 1],
              b: [0, (i & 4) >> 2, (j & 4) >> 2, (k & 4) >> 2],
            };

            let B = listOne({
              n: [...A.n],
              p: [...A.p],
              b: [...A.b],
            });

            let [a, b] = [toHex(A), toHex(B)];

            // console.log( "\nA:", debugPart(A), "\n a:", a.join(""), "\n B:", debugPart(B), "\n b:", b.join("") );

            DSet.add([a[2], a[3], "-", b[2]].join(""));
            DSet.add([a[1], a[2], "-", b[1]].join(""));
            DSet.add([a[0], a[1], "-", b[0]].join(""));

            let row = document.createElement("div");
            row.className = "row";

            drowPart(row, { ...A, h: a });
            drowGap(row, GAP_WIDTH, 4);
            drowPart(row, { ...B, h: b });

            rows.appendChild(row);
          }
        }
      }

      console.log("DSet", DSet);

      const patterns = [];
      Object.defineProperty(patterns, "debug", {
        get: function () {
          let str = "\n";
          for (let i = 0; i < this.length; i++) {
            str += `${i} ${this[i].join(",")}\n`;
          }
          return str;
        },
      });

      for (const pair of DSet) {
        let [v, k] = pair.split("-");
        if (!patterns[k]) patterns[k] = [];
        patterns[k].push(v);
      }

      // console.log("patterns", patterns.debug);
      code.textContent += "\n Patterns: \n";
      code.textContent += patterns.debug;

      /**
       * const matrix = new Array(patterns.length).fill(new Array(patterns.length).fill(0));
       * ！！！ 千万不能这么写 ！！！
       * 这样导致矩阵的每一行都是同一个引用，导致修改一个数组，其他行的数组都修改了
       *
       */
      const matrix = new Array(patterns.length);
      for (let i = 0; i < matrix.length; i++) {
        matrix[i] = new Array(patterns.length).fill(0);
      }
      Object.defineProperty(matrix, "debug", {
        get: function () {
          let str = "\n  0 1 2 3 4 5\n";
          for (let i = 0; i < this.length; i++) {
            str += `${i} ${this[i].join(" ")}\n`;
          }
          return str;
        },
      });

      for (let i = 0; i < patterns.length; i++) {
        for (let j = 0; j < patterns[i].length; j++) {
          let [m, n] = patterns[i][j].split("").map((e) => +e);
          matrix[m][n] = i;
        }
      }
      // console.log("matrix\n", matrix.debug);
      code.textContent += "\n Matrix: \n";
      code.textContent += matrix.debug;

      let matrixStr = matrix.debug;
      let matrix2Str = "";
      for (let i = 0; i < matrixStr.length; i++) {
        let h = Number.parseInt(matrixStr.at(i));
        if (!h) matrix2Str += matrixStr.at(i);
        else matrix2Str += 5 - ((h * 2) % 5);
      }

      code.textContent += "\n Swap:  0=>0,1=>3,2=>1,4=>2,3=>4,5=>5 \n";
      code.textContent += "\n Matrix2: \n";
      code.textContent += matrix2Str;
    })();
  </script>
</html>
